﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Hopscotch.Common.Logging;
using Hopscotch.Domain.Finance;
using Hopscotch.Presentation.Common;
using Hopscotch.Presentation.Controls.DataProviders;

namespace Hopscotch.Presentation.Controls.Core
{
    /// <summary>
    /// Interaction logic for InstrumentControl.xaml
    /// </summary>
    public partial class InstrumentControl : UserControlBase, INotifyDataErrorInfo
    {
        private readonly ILogger logger;
        DataErrorInfo internalError;

        public InstrumentControl()
        {
            InitializeComponent();
            this.logger = AppLogger.CreateLogger<InstrumentControl>();

            Validation.AddErrorHandler(this, OnBindingValidationError);

            //AutoCompleteBox does not support multiple ui threads
            //https://wpf.codeplex.com/workitem/18264
            instrumentAutoComplete.FilterMode = AutoCompleteFilterMode.None;
            instrumentAutoComplete.Populating += OnPopulating;
        }

        void OnBindingValidationError(object sender, ValidationErrorEventArgs e)
        {
            RaiseErrorsChanged("Instrument");
        }

        async void OnPopulating(object sender, PopulatingEventArgs e)
        {
            e.Cancel = true;
            AutoCompleteBox acb = e.Source as AutoCompleteBox;
            IInstrumentDataProvider instrumentDataProvider = DataProvider;
            internalError = null;

            if (instrumentDataProvider != null)
            {
                string search = e.Parameter;
                ICollection<Instrument> searchResults = null;

                try
                {
                    searchResults = await instrumentDataProvider.SearchAsync(search);
                    acb.ItemsSource = searchResults;
                    
                    acb.PopulateComplete();
                }
                catch (Exception ex)
                {
                    OnInstrumentSearchError(ex);
                }

            }
        }

        private void OnInstrumentSearchError(Exception ex)
        {
            logger.Error("Instrument search failed", ex);
            internalError = new DataErrorInfo(ErrorType.Error, ex.Message);
            RaiseErrorsChanged("Instrument");
        }

        //DependencyProperty(s)
        public static readonly DependencyProperty InstrumentIdProperty
              = DependencyProperty.Register("InstrumentId", typeof(int?), typeof(InstrumentControl), new PropertyMetadata(OnInstrumentIdChanged));

        private static readonly DependencyProperty InstrumentProperty
              = DependencyProperty.Register("Instrument", typeof(Instrument), typeof(InstrumentControl), new PropertyMetadata(OnInstrumentChanged));

        public static readonly DependencyProperty DataProviderProperty
              = DependencyProperty.Register("DataProvider", typeof(IInstrumentDataProvider), typeof(InstrumentControl), new PropertyMetadata(OnInstrumentDataProviderChanged));

        
        //Properties
        internal int? InstrumentId
        {
            get { return (int?)this.GetValue(InstrumentIdProperty); }
            set { this.SetValue(InstrumentIdProperty, value); }
        }

        public Instrument Instrument
        {
            get { return (Instrument)this.GetValue(InstrumentProperty); }
            set { this.SetValue(InstrumentProperty, value); }
        }

        public IInstrumentDataProvider DataProvider
        {
            get { return (IInstrumentDataProvider)this.GetValue(DataProviderProperty); }
            set { this.SetValue(DataProviderProperty, value); }
        }


        private async void LoadInstrument(int? instrumentId)
        {
            if (!instrumentId.HasValue || DataProvider == null)
            {
                Instrument = null;
                return;
            }

            if (Instrument != null &&
                Instrument.Id == instrumentId.Value)
                return;

            Instrument = await DataProvider.GetInstrument(instrumentId.Value);
        }

        private static void OnInstrumentIdChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var instrumentControl = obj as InstrumentControl;

            if (instrumentControl != null)
            {
                instrumentControl.LoadInstrument(instrumentControl.InstrumentId);
            }
        }

        private void ResetErrorText()
        {
            internalError = null;
        }

        private void ResetSearchText()
        {
            if (!instrumentAutoComplete.IsKeyboardFocusWithin)
                instrumentAutoComplete.Text = string.Empty;
        }

        private static void OnInstrumentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var instrumentControl = obj as InstrumentControl;
            if (instrumentControl == null)
                return;

            var instrument = e.NewValue as Instrument;
            if (instrument != null)
            {
                instrumentControl.ResetErrorText();
                instrumentControl.InstrumentId = instrument.Id;
            }
            else
            {
                instrumentControl.InstrumentId = null;
                instrumentControl.ResetSearchText();
            }
        }

        private static void OnInstrumentDataProviderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var instrumentControl = obj as InstrumentControl;

            if (instrumentControl != null)
            {
                instrumentControl.RaiseErrorsChanged("Instrument");
            }
        }

        public override IEnumerable GetErrors(string propertyName)
        {
            if (DataProvider == null)
            {
                //If the DataProvider has not been set, flag as an error
                return this.DataProviderError;
            }

            if (HasInternalError)
            {
                return new DataErrorInfo[] { internalError };
            }

            if (propertyName == "Instrument")
            {
                if (Validation.GetErrors(this).Count > 0)
                {
                    var list = Validation.GetErrors(this).Select(x => x.ErrorContent).ToList();
                    return list;
                }
            }

            return null;
        }

        private bool HasInternalError
        {
            get { return internalError != null; }
        }

        public override bool HasErrors
        {
            get
            {
                if (DataProvider == null || HasInternalError)
                {
                    return true;
                }

                return Validation.GetHasError(this);
            }
        }

    }
}
